`include "common.svh"
module rename #(
    parameter RENAME_WIDTH = 4,
    parameter COMMIT_WIDTH = 2,
    parameter GC_NUM = 64

) (
    input clk,
    input rst,
    // From Frontend
    input i_valid,
    input i_fop_valid[RENAME_WIDTH-1:0],
    input FrontendOP i_fop[RENAME_WIDTH-1:0],
    output i_ready,
    //To busy table
    output busytable_clear_valid[RENAME_WIDTH-1:0],
    output PRF_IDX busytable_clear_prf[RENAME_WIDTH-1:0],
    // To next Stage
    output o_valid,
    output RenamedOP o_rop[RENAME_WIDTH-1:0],
    output o_rop_valid[RENAME_WIDTH-1:0],
    input o_ready,
    // From Commit Stage
    input i_commit_valid[COMMIT_WIDTH-1:0],
    input COMMIT_TO_RENAME i_commit_data[COMMIT_WIDTH-1:0],
    output i_commit_ready
);


  //   //Pack i_fop_valid
  //   logic [RENAME_WIDTH-1:0] i_fop_valid_pack;
  //   pack #(1, RENAME_WIDTH) pack_i_fop_valid (
  //       .din (i_fop_valid),
  //       .dout(i_fop_valid_pack)
  //   );

  // Free List
  logic o_allocate_valid[RENAME_WIDTH-1:0];
  logic o_allocate_ready[RENAME_WIDTH-1:0];
  PRF_IDX o_allocate_entry[RENAME_WIDTH-1:0];
  FREELIST_IDX o_allocate_rptr[RENAME_WIDTH-1:0];
  logic i_redirect_valid;
  FREELIST_IDX i_redirect_rptr;
  PRF_IDX i_free_entry[COMMIT_WIDTH-1:0];
  logic i_free_valid[COMMIT_WIDTH-1:0];
  logic i_free_ready;
  freelist #(RENAME_WIDTH, COMMIT_WIDTH) inst_freelist (.*);
  assign i_commit_ready = i_free_ready;
  //Pack o_allocate_valid
  logic [RENAME_WIDTH-1:0] o_allocate_valid_pack;
  pack #(1, RENAME_WIDTH) pack_o_allocate_valid (
      .din (o_allocate_valid),
      .dout(o_allocate_valid_pack)
  );

  assign busytable_clear_valid = o_allocate_valid;
  assign busytable_clear_prf   = o_allocate_entry;

  // crat
  ARF_IDX ars1[RENAME_WIDTH-1:0];
  ARF_IDX ars2[RENAME_WIDTH-1:0];
  ARF_IDX ard[RENAME_WIDTH-1:0];
  PRF_IDX prs1[RENAME_WIDTH-1:0];
  PRF_IDX prs2[RENAME_WIDTH-1:0];
  PRF_IDX prd[RENAME_WIDTH-1:0];
  PRF_IDX allocate_prd[RENAME_WIDTH-1:0];
  logic rd_valid[RENAME_WIDTH-1:0];
  logic save_gc[RENAME_WIDTH-1:0];
  logic gc_full;
  logic commit_gc[COMMIT_WIDTH-1:0];
  logic restore_gc[COMMIT_WIDTH-1:0];
  crat_wrapper #(RENAME_WIDTH, COMMIT_WIDTH, GC_NUM) inst_crat (.*);

  // Pipeline Handshake
  wire freelist_ready = (&o_allocate_valid_pack);
  wire crat_ready = ~gc_full;

  wire decouple_o_valid;
  wire decouple_o_ready = o_ready;

  wire decouple_i_valid = i_valid & freelist_ready & crat_ready & (~i_redirect_valid);
  wire decouple_i_ready;

  wire decouple_i_fire = decouple_i_valid & decouple_i_ready;
  assign i_ready = decouple_i_ready & freelist_ready & crat_ready & (~i_redirect_valid);
  assign o_valid = decouple_o_valid;

  wire freelist_valid = decouple_i_fire;
  wire crat_valid = decouple_i_fire;
  // wire freelist_valid = crat_ready & (~i_redirect_valid);
  // wire crat_valid = freelist_ready & (~i_redirect_valid);

  decouple inst_decouple (
      .rst(rst | i_redirect_valid),
      .i_valid(decouple_i_valid),
      .i_ready(decouple_i_ready),
      .o_valid(decouple_o_valid),
      .o_ready(decouple_o_ready),
      .*
  );

  genvar gi;
  generate
    FREELIST_IDX restore_freelist_rptr[COMMIT_WIDTH-1:0];
    logic [COMMIT_WIDTH-1:0] restore_freelist_valid;

    logic free_valid[COMMIT_WIDTH-1:0];
    PRF_IDX free_entry[COMMIT_WIDTH-1:0];


    logic [$clog2(COMMIT_WIDTH)-1:0] free_idx[COMMIT_WIDTH-1:0];
    logic [$clog2(COMMIT_WIDTH):0] free_num;
    always_comb begin
      integer i;
      free_num = 'b0;
      for (i = 0; i < COMMIT_WIDTH; i = i + 1) begin
        free_idx[i] = 'b0;
      end
      for (i = 0; i < COMMIT_WIDTH; i = i + 1) begin
        if (free_valid[i]) begin
          free_idx[free_num[$clog2(COMMIT_WIDTH)-1:0]] = i[$clog2(COMMIT_WIDTH)-1:0];
          free_num = free_num + 'b1;
        end
      end
    end
    for (gi = 0; gi < COMMIT_WIDTH; gi = gi + 1) begin : RedirectCtrl
      assign free_valid[gi]   = i_commit_valid[gi] & i_commit_data[gi].has_rd;
      assign free_entry[gi]   = i_commit_data[gi].free_entry;

      assign i_free_valid[gi] = gi < free_num ? 'b1 : 'b0;  //free_valid[free_idx[gi]];
      assign i_free_entry[gi] = free_entry[free_idx[gi]];

      wire update_gc = i_commit_valid[gi] & i_commit_ready & i_commit_data[gi].has_gc;
      assign commit_gc[gi] = update_gc;
      assign restore_gc[gi] = update_gc & i_commit_data[gi].restore_gc;
      assign restore_freelist_valid[gi] = restore_gc[gi];
      assign restore_freelist_rptr[gi] = i_commit_data[gi].restore_freelist_rptr;
    end

    assign i_redirect_valid = |restore_freelist_valid;
    MUX_PRIO #(COMMIT_WIDTH, $size(
        FREELIST_IDX
    )) mux_freelist_rptr (
        .sel (restore_freelist_valid),
        .din (restore_freelist_rptr),
        .dout(i_redirect_rptr)
    );
    logic [$clog2(RENAME_WIDTH)-1:0] allocate_idx[RENAME_WIDTH-1:0];
    // always_comb begin
    //   integer i;
    //   allocate_idx[0] = 'b0;
    //   for (i = 1; i < RENAME_WIDTH; i = i + 1) begin
    //     allocate_idx[i] = (i_fop_valid[i]&i_fop[i].dop.has_rd) ? (allocate_idx[i-1] + 'b1) : allocate_idx[i-1];
    //   end
    // end
    // assign allocate_idx[0] = 'b0;
    // for (gi = 1; gi < RENAME_WIDTH; gi = gi + 1) begin : AllocateIndex
    //     assign allocate_idx[gi] = i_fop[gi].dop.has_rd ? (allocate_idx[gi-1] + 'b1) : allocate_idx[gi-1];
    // end

    logic [$clog2(RENAME_WIDTH):0] allocate_num;
    logic allocate_req[RENAME_WIDTH-1:0];
    always_comb begin
      integer i;
      allocate_num = 'b0;
      for (i = 0; i < RENAME_WIDTH; i = i + 1) begin
        allocate_idx[i] = 'b0;
        allocate_req[i] = i_fop_valid[i] & i_fop[i].dop.has_rd;
      end
      for (i = 0; i < RENAME_WIDTH; i = i + 1) begin
        if (allocate_req[i]) begin
          allocate_idx[i] = allocate_num[$clog2(RENAME_WIDTH)-1:0];
          allocate_num = allocate_num + 'b1;
        end else begin
          allocate_idx[i] = allocate_idx[i-1];
        end
      end
    end
    FREELIST_IDX freelist_rptr[RENAME_WIDTH-1:0];
    always_comb begin
      integer i;
      freelist_rptr[0] = i_fop[0].dop.has_rd ? (o_allocate_rptr[allocate_idx[0]] + 'b1) : o_allocate_rptr[allocate_idx[0]];

      for (i = 1; i < RENAME_WIDTH; i = i + 1) begin
        freelist_rptr[i] = i_fop[i].dop.has_rd ? (o_allocate_rptr[allocate_idx[i]] + 'b1) : freelist_rptr[i-1];
      end
    end
    // assign freelist_rptr[0] = i_fop[0].dop.has_rd ? (o_allocate_rptr[allocate_idx[0]] + 'b1) : o_allocate_rptr[allocate_idx[0]];

    // for (gi = 1; gi < RENAME_WIDTH; gi = gi + 1) begin : FreelistRptr
    //   assign freelist_rptr[gi] = i_fop[gi].dop.has_rd ? (o_allocate_rptr[allocate_idx[gi]] + 'b1) : freelist_rptr[gi-1];
    // end

    for (gi = 0; gi < RENAME_WIDTH; gi = gi + 1) begin : RenameOPGen
      wire need_allocate = i_fop_valid[gi] & i_fop[gi].dop.has_rd;
      assign o_allocate_ready[gi] = (gi < allocate_num) & freelist_valid;
      RenamedOP rop_next;
      assign rop_next.fop = i_fop[gi];
      assign rop_next.pdst = o_allocate_entry[allocate_idx[gi]];
      assign rop_next.freelist_rptr = freelist_rptr[gi];//i_fop[gi].dop.has_rd ? o_allocate_rptr[allocate_idx[gi]] + 'b1 : o_allocate_rptr[allocate_idx[gi]];
      assign rop_next.has_gc = save_gc[gi];
      assign rop_next.prs1 = prs1[gi];
      assign rop_next.prs2 = prs2[gi];
      assign rop_next.free_pdst = prd[gi];
      //Connect RAT Signal
      assign allocate_prd[gi] = o_allocate_entry[allocate_idx[gi]];
      assign ars1[gi] = i_fop[gi].dop.rs1;
      assign ars2[gi] = i_fop[gi].dop.rs2;
      assign ard[gi] = i_fop[gi].dop.rd;
      assign rd_valid[gi] = need_allocate & crat_valid;

      assign save_gc[gi] = decouple_i_fire & i_fop_valid[gi];
      reg_l #($size(
          RenamedOP
      )) rop_r (
          .load(decouple_i_fire),
          .din (rop_next),
          .dout(o_rop[gi]),
          .*
      );
      reg_lr #(1) rop_valid_r (
          .load(decouple_i_fire),
          .din (i_fop_valid[gi]),
          .dout(o_rop_valid[gi]),
          .*
      );
      // assign o_allocate_ready[gi] = allocate_valid ? i_fop_valid[gi] : 'b0;
    end
  endgenerate

  `PERF_EVENT(global_checkpoint_full, gc_full);
  `PERF_EVENT(freelist_busy, ~freelist_ready);
endmodule
